﻿#region Using Statements
using System;
using Microsoft.Xna.Framework;
#endregion

namespace StarFox.ScreenComponents
{
    /// <summary>
    /// Implementation of the StarShip's translation control.
    /// </summary>
    internal class StarShipTranslation
    {
        #region Fields

        IShipInput input;
        PlayerIndex playerIndex;
        Matrix transMatrix = Matrix.Identity;

        #endregion

        #region Position, Direction, Velocity

        // backing fields
        Vector3 position;
        Vector3 direction;

        #endregion

        #region Longitudinal Velocity

        /// <summary>
        /// Sets the minimum value used to determine the StarShip's longitudinal velocity.
        /// </summary>
        public float MinLongitudinalVelocity
        {
            private get;
            set;
        }

        /// <summary>
        /// Sets the maximum value used to determine the StarShip's longitudinal velocity.
        /// </summary>
        public float MaxLongitudinalVelocity
        {
            private get;
            set;
        }

        #endregion

        #region Vertical Velocity

        /// <summary>
        /// Sets the value used to determine the StarShip's vertical velocity.
        /// </summary>
        public float VerticalVelocity
        {
            private get;
            set;
        }

        #endregion

        #region Lateral Velocity

        /// <summary>
        /// Sets the value used to determine the StarShip's lateral velocity.
        /// </summary>
        public float LateralVelocity
        {
            private get;
            set;

        }

        #endregion
        
        #region Start, Stop

        bool stopped;

        /// <summary>
        /// Causes the StarShip to start moving.
        /// </summary>
        public void Start()
        {
            stopped = false;
        }

        /// <summary>
        /// Causes the StarShip to stop moving.
        /// </summary>
        public void Stop()
        {
            stopped = true;
        }

        #endregion

        #region Camera Shake

        bool isShake;
        float shakeMagnitude;
        TimeSpan shakeDuration;
        float shakeTime;
        Vector3 shakeOffset;

        /// <summary>
        /// Causes the StarShip to shake.
        /// </summary>
        /// <param name="shakeMagnitude">The magnitude of the shake.</param>
        /// <param name="shakeDuration">The duration of the shake.</param>
        public void Shake(float shakeMagnitude, TimeSpan shakeDuration)
        {
            this.shakeMagnitude = shakeMagnitude;
            this.shakeDuration = shakeDuration;
            shakeTime = 0.0f;
            isShake = true;
        }

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of this class.
        /// </summary>
        /// <param name="input">StarShip input management functions.</param>
        /// <param name="playerIndex">Specifies which player to read input from.</param>
        public StarShipTranslation(IShipInput input, PlayerIndex playerIndex)
        {
            this.input = input;
            this.playerIndex = playerIndex;
        }

        #endregion

        #region Translation

        /// <summary>
        /// Computes the StarShip's translation. 
        /// </summary>
        /// <param name="gameTime">Time elapsed since the last call to this method.</param>
        /// <param name="direction">The direction of translation.</param>
        /// <param name="velocity">The velocity of translation.</param>
        /// <returns>StarShip's translation.</returns>
        public Matrix ComputeShipTranslation(GameTime gameTime, Vector3 direction, float velocity)
        {
            this.direction = direction;

            #region Testing
#if DEBUG
            if (input.IsResetShipTranslation())
            {
                // reset all matrices
                transMatrix = Matrix.Identity;

                // reset positon
                position = Vector3.Zero;

                return transMatrix;
            }
#endif
            #endregion

            ShakeControl(gameTime);          
            
            if (!stopped)
            {
                VerticalControl();
                LateralControl();
                LongitudinalControl(velocity);
            }

            UpdateShipTransMatrix();

            return transMatrix;
        }

        private void VerticalControl()
        {
            #region Vertical Control

            if (input.IsMoveUp(playerIndex))
            {
                position.Y += VerticalVelocity;
            }
            else if (input.IsMoveDown(playerIndex))
            {
                position.Y -= VerticalVelocity;
            }

            #endregion
        }

        private void LateralControl()
        {
            #region Lateral Control

            if (input.IsMoveRight(playerIndex))
            {
                position.X += LateralVelocity;
            }
            else if (input.IsMoveLeft(playerIndex))
            {
                position.X -= LateralVelocity;
            }

            #endregion
        }

        private void LongitudinalControl(float velocity)
        {
            position += direction * (MinLongitudinalVelocity + (MaxLongitudinalVelocity - MinLongitudinalVelocity) * velocity);
        }

        private void ShakeControl(GameTime gameTime)
        {
            #region Shake Control

            if (isShake)
            {
                shakeOffset = new Vector3(StarFoxHelper.RandomFloat(-1.0f, 1.0f),
                                          StarFoxHelper.RandomFloat(-1.0f, 1.0f),
                                          StarFoxHelper.RandomFloat(-1.0f, 1.0f));

                shakeOffset *= shakeMagnitude * (1.0f - (float)Math.Pow(shakeTime, 2));

                // update timer
                float shakeDelta = (float)(gameTime.ElapsedGameTime.TotalMilliseconds / shakeDuration.TotalMilliseconds);
                shakeTime += shakeDelta;
                shakeTime = MathHelper.Clamp(shakeTime, 0.0f, 1.0f);
                if (shakeTime == 1.0f) { isShake = false; shakeTime = 0.0f; shakeOffset = Vector3.Zero; }
            }

            #endregion
        }

        private void UpdateShipTransMatrix()
        {
            #region Matrix Computation

            transMatrix = Matrix.CreateTranslation(position);

            if (isShake) { transMatrix *= Matrix.CreateTranslation(shakeOffset); }

            #endregion
        }

        #endregion
    }
}
